<?php
/**
 * Интерфейс "Описание URL адреса". 
 * Каждый класс, который вы планируете использовать для парсинга и генерации URL адреса должен реализовывать этот интерфейс.
 *
 * @package default
 * @author Alexander Sibiryakov
 **/
interface URLDescription {
//	public function __construct(RL_RequestRouter $router, Array $params);
	public function init();
	public function getURLElements(); // вернет массив содержащий последовательность имен элементов
	
	public function match($url);
	public function pack($request);
}

/**
 * Класс RL_RequestRouter_Regexp -- сопоставляет строку с шаблоном регулярного выражения
 *  пример описания URL адреса
 * 	new RL_RequestRouter_Regexp($router_instance, array(
 *		'(:base_url)/(:oid)/(:path)$',
 *		'module'   => 'Node',  
 *		'action' => 'DeleteObject',
 *		'path' => 'del',
 *		'oid' => '[0-9]+'
 * 	  ));
 * где в первом элементе массива первая строка - основной паттерн урла, 
 * (:*) - элемент адреса, после двоеточия указывается его имя,
 * дальше в ассоциативном массиве для элементов указывается паттерн, 
 * по которому этот элемент будет парситься и генерироваться.
 *
 * Если для элемента не указать паттерн, то при парсинге и генерации он будет искаться в массиве $defaults экземпляра
 * RL_RequestRouter, там хранятся дефолтные паттерны.
 */
class RL_RequestRouter_Regexp implements URLDescription
{
	/**
	 * После компиляции содержит все элементы паттерна пример (:element)
	 *
	 * @var string
	 **/
	private $idx2params = array();
	
	public function __construct(RL_RequestRouter $router, Array $params)
	{
		$this->router  = $router;
		$this->pattern = $params[0]; unset($params[0]);
		$this->params  = $params;
		$this->regexp  = NULL;
	}

	/**
	 * Сопоставить $url с правилами и вернуть (дерево разбора?)
	 *
	 * $o = new RL_RequestRouter_Url(array(
	 *       "(:user)/and/(:user)",              'user'      => '[a-zA-Z]+',
	 *                                           '_action'   => 'Users.me',
	 *    )),
	 * $matches = $o->match('hannah/and/hannah');
	 * @param string $url
	 * @return array or NULL
	 */
	public function match($url)
	{
		$this->init();
		$url = current(explode('?', $url, 2));
		$request = NULL;
		if (preg_match('#'.$this->regexp.'#', $url, $matches)) {
			unset($matches[0]); // пропускаем собственно совпадение
			$request = $this->params;
			foreach ($matches as $k=>$v) {
				$request[$this->idx2params[$k-1]] = $v;
			}
		}
		return $request;
	}

	/**
	 * Сопоставить запрос набору правил, вернуть урл и остаток запроса
	 *
	 * @return array ($url, $request) or NULL
	 */
	public function pack($request)
	{
		$res = NULL;

		$url = $this->pattern;

		// rest of request
		$what     = array(); // what replace
		$replace  = array(); // replace to
		foreach ($this->idx2params as $k => $param_name) { // подставляем параметры в шаблон
			if (!array_key_exists($param_name, $request)) { // нет такого параметра => это не тот шаблон
				$url = NULL;
				break;
			}

			$re = $this->idx2regexp[$k];
			$value = $request[$param_name];
			if (!preg_match('#'.$re.'#', $value)) { // параметра не соответствует регекспу => это не тот шаблон
				$url = NULL;
				break;
			}
			$what[]    = '(:'.$param_name.')';
			$replace[] = /*urlencode*/($value);
		}
		foreach($this->params as $k => $v) {
			if (!array_key_exists($k, $request)) { // нет такого параметра => это не тот шаблон
				$url = NULL;
				break;
			}
		}
		if ($url) {
			$res = str_replace($what, $replace, $url);
			$res = rtrim($res, '$');
		}

		return $res;
	}

	public function init() {
		if (count($this->idx2params)==0) {
			$this->compilePatternRegexp();
		}
	}
	
	public function getURLElements() {
		return $this->idx2params;
	}
	
	/**
	 * $this->pattern -> $this->regexp
	 */
	private function compilePatternRegexp()
	{
		if (!isset($this->regexp)) {
			$callback = array(&$this, '__callback_compilePatternRegexp');
			$this->idx2params = array();
			$this->regexp = preg_replace_callback('#\\(:([\\w]+)\\)#',  
				$callback, 
				$this->pattern);
		}
		return $this->regexp;
	}

	private function __callback_compilePatternRegexp($matches)
	{
		$param_name = $matches[1];
		if (array_key_exists($param_name, $this->params)) {
			$res = '('.$this->params[$param_name].')'; // имя было в скобках
			$this->idx2params[] = $param_name;
			$this->idx2regexp[] = $res;
		} elseif (array_key_exists($param_name, $this->router->defaults)) {
			$res = '('.$this->router->defaults[$param_name].')'; // имя было в скобках
			$this->idx2params[] = $param_name;
			$this->idx2regexp[] = $res;
		} else {
			$res = $matches[0];
		}
		return $res;
	}

}


